#File: rondamon_interpreter.py - Interpreter for the Rondamon Language
#JGordon & Co

from rondamon import parse, RondamonException
from fun_table import fun_table
from rdhtml_generator import process_file
from StringIO import StringIO
import sys

#auxiliary python functions 
from python_functions import r_concat, r_extend, r_mod, r_pow, r_isnumber, r_isstring, r_tostring, r_tonumber, \
r_ceil, r_floor, r_append, r_insert, r_remove, r_sort, r_reverse, r_pop, r_index, r_max, r_min, \
r_get_parameter_value, r_get_parameter_values, r_get_parameter_names, r_get_header_value, \
r_get_header_names, r_set_header_value, r_get_session_value, r_set_session_value, r_get_persistent, r_set_persistent, \
r_pwebstring, r_escapeHTML

###########################################################

class RdmReturnException(Exception):
    def __init__(self,value):
        self.value = value
    def __str__(self):
        return repr(self.value)
    
class RdmBreakException(Exception):
    pass

############################################################

def eval_expr(table, expr):

    #Numeros
    if isinstance(expr, int) or isinstance(expr, float):
        return expr

    #empty list
    if expr == []:
        return expr

    #Cadenas y Variables
    if isinstance(expr, str):
        if (expr[0]=='"'):
	    return expr[1:-1]	
	elif expr in table:
            return table[expr]
        else:
            raise RondamonException("Undefined variable: '%s'" % expr) 

    #Refactorizacion requerida---------------------------------------------------
    #Expresiones - Operadores unarios
    if isinstance(expr, list) and not isinstance(expr[0], list) and (expr[0] in uniop_dict) and len(expr) == 2 :
        return uniop_dict[expr[0]](table, eval_expr(table, expr[1]))
    #Expresiones - Operadores binarios
    elif isinstance(expr, list) and not isinstance(expr[0], list) and (expr[0] in binop_dict):
	return binop_dict[expr[0]](eval_expr(table, expr[1]), eval_expr(table, expr[2]))
    #Expresiones - Enunciados
    elif isinstance(expr, list) and not isinstance(expr[0], list) and (expr[0] in expsta_dict):
        if expr[0] =='call':
            return expsta_dict[expr[0]](table, [eval_expr(table, expr[1]), [eval_expr(table,s) for s in expr[2]]])
        else:
            return expsta_dict[expr[0]](table, [eval_expr(table, expr[1]), eval_expr(table, expr[2])])
        #return expsta_dict[expr[0]](table, [eval_expr(table, expr[1]), eval_expr(table, expr[2])])
    else:
	return expr
    #--------------------------------------------------------------------------------

############################################################

def execute_print(table, args):
    print eval_expr(table, args[0])

def execute_return(table, args):
    raise RdmReturnException(eval_expr(table,args[0]))

def execute_break(table,args):
    raise RdmBreakException("Break")

def execute_if(table, args):
    if eval_expr(table, args[0]):
        execute_statements(table, args[1])

def execute_ifelse(table, args):
    if eval_expr(table, args[0]):
        execute_statements(table, args[1])
    else:
	execute_statements(table, args[2])
	
def execute_while(table, args):
    try:
        while eval_expr(table, args[0]):
            execute_statements(table, args[1])
    except RdmBreakException:
        pass

def execute_assign(table, args):
    element = eval_expr(table, args[1])
    if isinstance(element,list):
        for i in range(len(element)):
            if isinstance(element[i],str) and element[i] in table:
                element[i] = eval_expr(table, element[i])
        table[args[0]] = element ##beta
    else:
        table[args[0]] = eval_expr(table, args[1])
    #table[args[0]] = eval_expr(table, args[1])

def execute_fu(table,args):
    table[args[0]] = [args[1],args[2]]

def execute_call(table,args):
    if isinstance(args[0],list) or isinstance(args[0],str):
        try:
            #Llamada con elementos de la funcion como parametro
            if isinstance(args[0],list):
                if len(args[1]) == len(args[0][0]):
                    locales = {}
                    for i in range(len(args[1])):
                        locales[args[0][0][i]] = args[1][i]
                    env = fun_table(table,locales)
                    execute_statements(env,args[0][1])
                else: 
                    raise RondamonException("Bad number of arguments: '%s'" % args[0])
            #Llamada con variable de funcion como parametro
            elif args[0] in table:
                elementos_fun = table[args[0]]
                if len(args[1]) == len(elementos_fun[0]):
                
                    locales = {}
                    for i in range(len(args[1])):
                        locales[elementos_fun[0][i]] = args[1][i]
                    env = fun_table(table,locales)
                    execute_statements(env,elementos_fun[1])
                else: 
                    raise RondamonException("Bad number of arguments: '%s'" % args[0])
            #Funcion no definida
            else:
                raise RondamonException("Undefined function: '%s'" % args[0])
        except RdmReturnException,e:
            return e.value
    else:
        x = args[0](args[1],table)
        if x or x==False:
            return x

def execute_length(table,arg):
    if isinstance(arg,str) or isinstance(arg,list):
        return len(arg)
    else:
        raise RondamonException("Bad parameter to # operator. String or list expected, got: '%s'" % arg)

def execute_index(table,args):
    if isinstance(args[0],list) or isinstance(args[0],str):
        return (args[0][args[1]])
    else:
        raise RondamonException("Index - bad parameter : '%s'" % args[0])	

def execute_statements(table, statements):
    for s in statements:
        if s[0] == 'call':
            eval_expr(table,s)
        else:
            statement_dict[s[0]](table, s[1:])

##############################################################

def create_table(varlst):
    d = {}
    for var in varlst:
        d[var] = 0
    return d

uniop_dict = {
    '-': lambda x,y: -y,
    '#': execute_length,
}

expsta_dict = {
    'index': execute_index,
    'call' : execute_call,
}
    
binop_dict = {
    '<': lambda x, y: (x < y),
    '>': lambda x, y: (x > y),
    '==': lambda x, y: (x == y),
    '~=': lambda x, y: (x != y),
    '+': lambda x, y: x + y, 
    '-': lambda x, y: x - y,
    '*': lambda x, y: x * y,
    '/': lambda x, y: x / y,
    'and' : lambda x,y: x and y,
    'or' : lambda x,y: x or y,
}
    
statement_dict = {
    'print': execute_print,
    'if': execute_if,
    'ifelse': execute_ifelse,
    'while': execute_while,
    'fu': execute_fu,
    'return': execute_return,
    '=': execute_assign,
    '#': execute_length,
    'call' : execute_call,
    'index' : execute_index,
    'break' : execute_break,

    
}

def load_python_function(table,fu,name):
    table[name] = fu
    
##########forward external function################################
def forward(args,table):
    if len(args) != 1:
        raise RondamonException("forward() takes exactly 1 argument")
    if not isinstance(args[0], str):
        raise RondamonException("forward(): string expected")
    code = process_file(args[0])
    pcode = parse(code)
    execute_statements(table, pcode[0])
##################################################################

def interpret(rdminput,table=create_table([])):
    ast = parse(rdminput)
    global_table = table #create_table([])
    
    #load python functions here##########
    load_python_function(global_table,r_concat,"strconcat")
    load_python_function(global_table,r_extend,"lextend")
    load_python_function(global_table,r_mod,"mod")
    load_python_function(global_table,r_pow,"pow")
    load_python_function(global_table,r_isnumber,"isnumber")
    load_python_function(global_table,r_isstring,"isstring")
    load_python_function(global_table,r_tostring,"tostring")
    load_python_function(global_table,r_tonumber,"tonumber")
    load_python_function(global_table,r_ceil,"ceil")
    load_python_function(global_table,r_floor,"floor")
    load_python_function(global_table,r_append,"lappend")
    load_python_function(global_table,r_insert,"linsert")
    load_python_function(global_table,r_remove,"lremove")
    load_python_function(global_table,r_sort,"lsort")
    load_python_function(global_table,r_reverse,"lreverse")
    load_python_function(global_table,r_pop,"lpop")
    load_python_function(global_table,r_index,"lindex")
    load_python_function(global_table,r_max,"lmax")
    load_python_function(global_table,r_min,"lmin")
    load_python_function(global_table,forward,"forward")
    load_python_function(global_table,r_pwebstring,"pwebstring")
    load_python_function(global_table,r_escapeHTML,"escapeHTML")
    load_python_function(global_table,r_get_parameter_value,"get_parameter_value")
    load_python_function(global_table,r_get_parameter_values,"get_parameter_values")
    load_python_function(global_table,r_get_parameter_names,"get_parameter_names")
    load_python_function(global_table,r_get_header_value,"get_header_value")
    load_python_function(global_table,r_get_header_names,"get_header_names")
    load_python_function(global_table,r_set_header_value,"set_header_value")
    load_python_function(global_table,r_get_session_value,"get_session_value")
    load_python_function(global_table,r_set_session_value,"set_session_value")
    load_python_function(global_table,r_get_persistent,"get_persistent")
    load_python_function(global_table,r_set_persistent,"set_persistent")
    #####################################
    
    execute_statements(global_table, ast[0])
    return global_table

def interpret2string(code,table=create_table([])):
    output = StringIO()
    sys.stdout = output

    dict = interpret(code,table)

    console = output.getvalue()
    output.close()
    sys.stdout = sys.__stdout__
    
    return console,dict
